﻿/*
    Project: IV:MP GUI Editor (http://code.google.com/p/ivmp-gui-editor)
    Code license: GNU Lesser GPL (http://www.gnu.org/copyleft/lesser.html) 
 */

using System;
using System.IO;
using System.Text;
using System.Diagnostics;
using System.Threading;
using System.Collections.Generic;

namespace IvmpEditor
{
    class SquirrelCompiler : ICompiler
    {
        public static readonly LockFile lockFile = new LockFile("squirrel_compiler.lock"); // todo for specified version exe?
        public const string C_DEFAULT_EXE = "sq_v304_ivmp.exe";

        bool abort, compiledOk;
        DirectoryInfo workDirectory, scriptDirectory;
        string scriptName, logStr;

        public SquirrelCompiler(string appTempDirectory)
        {
            workDirectory = new DirectoryInfo(appTempDirectory);
        }

        public bool IsCompileDone
        {
            get { return compiledOk; }
        }
        public bool IsCompileAborted
        {
            get { return !IsCompileDone && abort; }
        }
        public string EXEName
        {
            get;
            set;
        }
        public string InfoText
        {
            get { return string.Format("Squirrel (c) squirrel-lang.org - {0}", EXEName); }
        }

        public event _Compiler.SendString SetStatus;
        public event _Compiler.SendString FatalError;
        public event _Compiler.Signal CompilationStarted;
        public event _Compiler.Signal CompilationFinished;

        public void Compile(string sourceFileName, Encoding src_encd)
        {
            // Check for source file extension
            if (!sourceFileName.ToLower().EndsWith(".nut"))
                throw new NotSupportedException("This is not Squirrel script source file (.nut)?");

            compiledOk = abort = false;
            logStr = string.Empty;
            CompilationStarted(this);
            try
            {
                // Lock our lock file to prevent running several compiler instances
                _Compiler.HoldCompilerLock(lockFile, ref abort);

                // Get scriptname & etc info.
                scriptDirectory = new FileInfo(sourceFileName).Directory;
                int i = sourceFileName.LastIndexOf("\\") + 1;
                scriptName = sourceFileName.Substring(i, sourceFileName.Length - i);
                scriptName = scriptName.Substring(0, scriptName.ToLower().LastIndexOf(".nut"));
                var resultFileName = scriptDirectory + @"\" + scriptName + ".cnut";
                if (scriptName == "out") // fixme
                    throw new ArgumentException("Cannot compile script with name \"out.nut\". Please rename your script and try again.");
                if (File.Exists(resultFileName))
                    File.Delete(resultFileName);

                // Create SQ.exe process:
                var sqCompiler = new Process();
                sqCompiler.StartInfo.FileName = string.Format(@"{0}\{1}", workDirectory, EXEName ?? C_DEFAULT_EXE);
                sqCompiler.StartInfo.WorkingDirectory = scriptDirectory.FullName;
                sqCompiler.StartInfo.Arguments = string.Format("-c \"{0}\"", scriptName + ".nut");
                sqCompiler.StartInfo.CreateNoWindow = true;
                sqCompiler.StartInfo.UseShellExecute = false;
                sqCompiler.StartInfo.RedirectStandardOutput =  
                    sqCompiler.StartInfo.RedirectStandardError = true;
                sqCompiler.StartInfo.StandardOutputEncoding =
                    sqCompiler.StartInfo.StandardErrorEncoding = src_encd;
                sqCompiler.EnableRaisingEvents = true;
                sqCompiler.OutputDataReceived += sq_OutputDataReceived;
                sqCompiler.ErrorDataReceived += sq_OutputDataReceived;

                // Compile:
                sqCompiler.Start();
                sqCompiler.PriorityClass = ProcessPriorityClass.Idle;
                sqCompiler.BeginErrorReadLine();
                sqCompiler.BeginOutputReadLine();
                if (!_Compiler.WaitForProcessExit(ref abort, sqCompiler))
                    throw new ThreadStateException();

                // Finish:
                if (logStr == string.Empty)
                    compiledOk = true;
                if (compiledOk)
                {
                    // Rename resultative "out.cnut" file
                    File.Move(scriptDirectory + @"\out.cnut", resultFileName);
                }
            }
            catch (Exception ex)
            {
                // Fatal error (something went wrong ...)
                compiledOk = false;
                this.FatalError(this, ex.Message);
            }

            // Cleanup, unlock, finish
            Cleanup();
            lockFile.Release();
            CompilationFinished(this);
        }
        public void RequestAbort() { abort = true; }

        void sq_OutputDataReceived(object sender, DataReceivedEventArgs e)
        {
            if (e.Data == null)
                return;

            // Triggered when SQ.exe outputs to console (or error)
            var logLine = e.Data.Trim() + Environment.NewLine;
            logStr += logLine;
            SetStatus(this, logLine);
        }
        void Cleanup()
        {
            if (!compiledOk)
            {
                try { File.Delete(workDirectory.FullName + @"\out.cnut"); } catch { }
                try { File.Delete(workDirectory.FullName + @"\" + scriptName + ".cnut"); } catch { }
            }
        }

        public static string[] ListAvailableSquirrelCompilers(string workDir)
        {
            var sqCs = Directory.GetFiles(workDir, "sq_*.exe");
            for (int i = 0; i < sqCs.Length; i++)
            {
                var i2 = sqCs[i].LastIndexOf('\\') + 1;
                sqCs[i] = sqCs[i].Substring(i2, sqCs[i].Length - i2);
            }
            return sqCs;
        }
    }
}
